/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package main;

import java.util.ArrayList;
import java.util.List;
import javax.media.opengl.GL;
import object.CS455Matrix;
import object.Color;
import object.Point;
import object.Polygon;
import object.Shape;
import object.Sphere;

/**
 *
 * @author dheath
 */
public class RayTracing
{

    private Point light;

    public RayTracing()
    {
        light = new Point(1f, 1f, 1f, 1);
    }

    public void rayTrace(GL gl, List<Shape> shapes, CS455Matrix M, Point eye)
    {

        CS455Matrix inv = new CS455Matrix(M.inverse());

        gl.glBegin(GL.GL_POINTS);

        for (int x = 0; x < 800; x++)
        {
            for (int y = 0; y < 800; y++)
            {
                Point pix = new Point(x, y, 0, 1);
                Point pixWorld = inv.multiplyPoint(pix);
                //pixWorld.setX(pixWorld.getX()*-1);
                //pixWorld.setY(pixWorld.getY()*-1);

                Point ray = pixWorld.minus(eye);
                ray.normalize();

                Color c = primaryRay(eye, ray, shapes);
                //System.out.println("("+x+","+y+")");
                gl.glColor3f(c.getRed(), c.getGreen(), c.getBlue());
                gl.glVertex3f(x, y, 0);
            }
        }

        gl.glEnd();
    }

    private Color primaryRay(Point eye, Point ray, List<Shape> shapes)
    {
        boolean intersection = false;
        Point closest = null;

        Shape shp = null;

        for (Shape shape : shapes)
        {
            Point inter = doesIntersect(eye, ray, shape);

            if (inter != null)
            {
                intersection = true;
            } else
            {
                continue;
            }

            if (closest == null)
            {
                closest = inter;
                shp = shape;
            } else
            {
                if (inter.distance(eye) < closest.distance(eye))
                {
                    closest = inter;
                    shp = shape;
                }
            }
        }

        if (!intersection)
        {
            return new Color((float) 0, (float) 0, (float) 0);
        }

        Point norm;// = new Point(0,1,0,1);

        if (shp instanceof Sphere)
        {
            Sphere sph = (Sphere) shp;
            norm = closest.minus(sph.getCenter());
            //Point norm = sph.getCenter().minus(closest);//  
            norm.normalize();

        } else
        {
            Polygon polygon = (Polygon) shp;
            norm = new Point(0, 1, 0, 1);

        }

        boolean shadow = shadowRay(closest.add(norm.times(.001f)), light, shapes, shp);

        Color color = phongShading(norm, eye, ray, closest, shadow, shp.getColor());
        return color;
    }

    private boolean shadowRay(Point origin, Point vector, List<Shape> shapes, Shape shp)
    {
        for (Shape shape : shapes)
        {
           /* if (shp == shape)
            {
                continue;
            }
*/

            Point inter = doesIntersect(origin, vector, shape);
            if (inter != null)
            {
                return true;
            }

        }
        return false;
    }

    private Point doesIntersect(Point eye, Point ray, Shape shape)
    {
        if (shape instanceof Sphere)
        {
            return sphereIntersection(eye, ray, (Sphere) shape);
        } else
        {
            return polygonIntersection(eye, ray, (Polygon) shape, ((Polygon)shape).getNormal());
        }
    }

    private Point polygonIntersection(Point eye, Point ray, Polygon polygon,Point norm)
    {
        //Point norm = polygon.getNormal();
        float T = 0;
        
        float d = polygon.getDistanceOrigin();
        
        float dotp = norm.dot(ray);
        
        if(dotp == 0)
        {
            return null;
        }
        if(dotp > 0)
        {
            return null;// polygonIntersection(eye,ray,polygon, norm.times(-1));
        }
        
        T = -1*(norm.dot(eye)+d) / dotp;
        if(T <= 0)
        {
            return null;
        }
        
        Point inter = eye.add(ray.times(T));
        
        if(onPolygon(inter, polygon))
        {
            return inter;
        }
        else
        {
            return null;
        }
    }
    
    private boolean onPolygon(Point point, Polygon polygon)
    {
        Point norm = polygon.getNormal();
        
        float x = (float)Math.abs(norm.getX());
        float y = (float)Math.abs(norm.getY());
        float z = (float)Math.abs(norm.getZ());
        
        List<Point> working = new ArrayList();
        Point twoD;
        if(x >= y && x >= z )
        {
            twoD = new Point(point.getY(),point.getZ(),0,1);
            for(Point p : polygon.getPoints())
            {
                Point n = new Point(p.getY(),p.getZ(),0,1);
                working.add(n);
            }
        }
        else if(y >= x && y >= z)
        {
            twoD = new Point(point.getX(),point.getZ(),0,1);
            for(Point p : polygon.getPoints())
            {
                Point n = new Point(p.getX(),p.getZ(),0,1);
                working.add(n);
            }
        }
        else
        {
            twoD = new Point(point.getX(),point.getY(),0,1);
            for(Point p : polygon.getPoints())
            {
                Point n = new Point(p.getX(),p.getY(),0,1);
                working.add(n);
            }
        }
        List<Point> working2 = new ArrayList();
        for(Point p : working)
        {
            Point n = p.minus(twoD);
            working2.add(n);
        }
        
        int numCrossings = 0;
        
        int signHolder = (working2.get(0).getY() < 0 ? -1 : 1);
        int nextSignHolder;
        
        for(int i = 0; i < working2.size(); i++)
        {
            
            int j = i+1;
            if(j == working.size())
            {
                j = 0;
            }
            Point a = working2.get(i);
            Point b = working2.get(j);
            
            if(b.getY() < 0)
            {
                nextSignHolder = -1;
            }
            else
            {
                nextSignHolder = 1;
            }
            
            if(signHolder != nextSignHolder)
            {
                if(a.getX() > 0 && b.getX() > 0)
                {
                    numCrossings++;
                }
                else if(a.getX() > 0 || b.getX() > 0)
                {
                    float ucross = a.getX() - a.getY() * (b.getX() - a.getX())/(b.getY()-a.getY());
                    if(ucross > 0)
                    {
                        numCrossings++;
                    }
                }
            }
            signHolder = nextSignHolder;
            
            
        }
        
        return (numCrossings%2 == 1);
        
    }
    private Point sphereIntersection(Point eye, Point ray, Sphere sphere)
    {

        float T = 0;

        float A = 1;

        float B = 2 * (ray.getX() * eye.getX() - ray.getX() * sphere.getCenter().getX() +
                ray.getY() * eye.getY() - ray.getY() * sphere.getCenter().getY() +
                ray.getZ() * eye.getZ() - ray.getZ() * sphere.getCenter().getZ());

        float C = eye.getX() * eye.getX() - 2 * eye.getX() * sphere.getCenter().getX() +
                sphere.getCenter().getX() * sphere.getCenter().getX() +
                eye.getY() * eye.getY() - 2 * eye.getY() * sphere.getCenter().getY() +
                sphere.getCenter().getY() * sphere.getCenter().getY() +
                eye.getZ() * eye.getZ() - 2 * eye.getZ() * sphere.getCenter().getZ() +
                sphere.getCenter().getZ() * sphere.getCenter().getZ() -
                sphere.getRadius() * sphere.getRadius();

        float disc = (float) Math.sqrt(B * B - 4 * C);

        if (disc == 0)
        {
            //intersection = true;
            T = -1 * B / 2;
        } else
        {
            if (disc > 0)
            {
                //intersection = true;
                float t0 = (-1 * B - disc) / 2;
                float t1 = (-1 * B + disc) / 2;

                if (t0 < 0 && t1 > 0)
                {
                    T = t1;
                } else
                {
                    if (t0 > 0 && t1 > 0)
                    {
                        T = t0;
                    } else
                    {
                        return null;
                    }
                }
            } else
            {
                return null;
            }
        }


        Point inter = eye.add(ray.times(T));

        return inter;
    }

    private Color phongShading(Point n, Point eye, Point ray, Point pix, boolean shadow, Color color)
    {
        Point e = eye.minus(pix);

        Point l = light;

        l.normalize();
        e.normalize();
        n.normalize();

        float S = 1f;
        float Ca = .6f;
        if (shadow)
        {
            //System.out.println("sahdow");
            S = 0f;
           // Ca = (float) .3;
        }
        //float Ca = (float) .6;// * S;//.6;
        float Cl = (float) .9;
        float Cp = (float) 1;
        float p = 8;
        //Cr(Ca + Cl max(0, n.l))+ ClCpmax(0, e.r)p
        //r = 2n(n.l)-l
        Point r = n.times(2).times(n.dot(l)).minus(l);
        r.normalize();
        l.normalize();
        e.normalize();
        n.normalize();
        //System.out.prinln()

        float last = Cl * Cp * S * (float) Math.pow(max(0, e.dot(r)), p);
        float first = (Ca + Cl * S * max(0, n.dot(l)));

        float red = color.getRed() * first + last;//*polyC.getRed();
        float green = color.getGreen() * first + last;//*polyC.getGreen();
        float blue = color.getBlue() * first + last;//*polyC.getBlue();

        if (red > 1)
        {
            red = 1;
        }
        if (blue > 1)
        {
            blue = 1;
        }
        if (green > 1)
        {
            green = 1;
        }

        return new Color(red, green, blue);
    }

    private float max(float a, float b)
    {
        return (b > a ? b : a);
    }
}
